﻿using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace UILibrary
{
    /// <summary>
    /// 扇形
    /// 通过path实现，支持角度，外半径，内半径，进度(由内到外的进度条效果)， 边框(外，内，底)，内嵌slot（外,中，里)，角边距
    /// lys 2024.05.22
    /// </summary>
    public partial class CanvasArc : Canvas
    {
        private Path bgPath {  get; set; }
        private Path fillPath { get; set; }
        private Path borderPath { get; set; }
        private Canvas slotBody { get; set; }
        //private Canvas slotSign { get; set; }
        /// <summary>
        /// 插槽,位置在扇形的中心点
        /// </summary>
        public FrameworkElement slot
        {
            get { return (FrameworkElement)GetValue(slotProperty); }
            set { SetValue(slotProperty, value); }
        }

        // Using a DependencyProperty as the backing store for slot.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty slotProperty =
            DependencyProperty.Register("slot", typeof(FrameworkElement), typeof(CanvasArc), new PropertyMetadata(null));

        public FrameworkElement slotInner
        {
            get { return (FrameworkElement)GetValue(slotInnerProperty); }
            set { SetValue(slotInnerProperty, value); }
        }

        // Using a DependencyProperty as the backing store for slot.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty slotInnerProperty =
            DependencyProperty.Register("slotInner", typeof(FrameworkElement), typeof(CanvasArc), new PropertyMetadata(null));

        public FrameworkElement slotOuter
        {
            get { return (FrameworkElement)GetValue(slotOuterProperty); }
            set { SetValue(slotOuterProperty, value); }
        }

        // Using a DependencyProperty as the backing store for slot.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty slotOuterProperty =
            DependencyProperty.Register("slotOuter", typeof(FrameworkElement), typeof(CanvasArc), new PropertyMetadata(null));

        public CanvasArc()
        {
            bgPath = new Path() { 
                //Stroke= new SolidColorBrush(Colors.Red),
                //Fill= new SolidColorBrush(Colors.Blue),
                //StrokeThickness =1
            };


            fillPath = new Path()
            {
                //StrokeThickness = BorderWidth,
                //Stroke = new SolidColorBrush(Colors.Transparent),

            };
            borderPath = new Path()
            {
                StrokeThickness = BorderWidth,
                Stroke = new SolidColorBrush(Colors.Transparent),

            };

            fillPath.IsHitTestVisible = false;
            SetLeft(fillPath, 0);
            SetTop(fillPath, 0);
            SetLeft(bgPath, 0);
            SetTop(bgPath, 0);
            SetLeft(borderPath, 0);
            SetTop(borderPath, 0);

            slotBody = new Canvas() {};
            //slotBody.Background = new SolidColorBrush(Colors.DarkGray);

            var cp = new ContentPresenter();
            var cpTransform = new TranslateTransform();
            cp.RenderTransform = cpTransform;
            cp.SetBinding(ContentPresenter.ContentProperty, new Binding("slot") { Source=this  });

            var cpInner = new ContentPresenter();
            var cpInnerTransform = new TranslateTransform();
            cpInner.RenderTransform = cpInnerTransform;
            cpInner.SetBinding(ContentPresenter.ContentProperty, new Binding("slotInner") { Source = this });

            var cpOuter = new ContentPresenter();
            var cpOuterTransform = new TranslateTransform();
            cpOuter.RenderTransform = cpOuterTransform;
            cpOuter.SetBinding(ContentPresenter.ContentProperty, new Binding("slotOuter") { Source = this });


            slotBody.Children.Add(cp);
            slotBody.Children.Add(cpInner);
            slotBody.Children.Add(cpOuter);


            Children.Add(bgPath);
            Children.Add(fillPath);
            Children.Add(borderPath);


            Children.Add(slotBody);

            cp.SizeChanged += (o, e) =>
            {
                var slotEl = o as FrameworkElement;
                if (slotEl != null)
                {
                    cpTransform.X = -cp.ActualWidth/2 ;
                    cpTransform.Y = -cp.ActualHeight/2 ;

                }

            };

            cpInner.SizeChanged += (o, e) =>
            {
                var slotEl = o as FrameworkElement;
                if (slotEl != null)
                {
                    cpInnerTransform.X = -cpInner.ActualWidth / 2;
                    cpInnerTransform.Y = -cpInner.ActualHeight / 2;

                }

            };

            cpOuter.SizeChanged += (o, e) =>
            {
                var slotEl = o as FrameworkElement;
                if (slotEl != null)
                {
                    cpOuterTransform.X = -cpOuter.ActualWidth / 2;
                    cpOuterTransform.Y = -cpOuter.ActualHeight / 2;

                }

            };

        }


        /// <summary>
        /// 角度开始
        /// </summary>
        public double AngleStart
        {
            get { return (double)GetValue(AngleStartProperty); }
            set
            {
                SetValue(AngleStartProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for Angle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AngleStartProperty =
            DependencyProperty.Register("AngleStart", typeof(double), typeof(CanvasArc), new PropertyMetadata(0.0, PathArcChangedCallback));

        /// <summary>
        /// 角度开始
        /// </summary>
        public double AngleEnd
        {
            get { return (double)GetValue(AngleEndProperty); }
            set
            {
                SetValue(AngleEndProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for Angle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AngleEndProperty =
            DependencyProperty.Register("AngleEnd", typeof(double), typeof(CanvasArc), new PropertyMetadata(15.0, PathArcChangedCallback));


        /// <summary>
        /// 间距角度
        /// </summary>
        public double AngleSpace
        {
            get { return (double)GetValue(AngleSpaceProperty); }
            set
            {
                SetValue(AngleSpaceProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for Space.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AngleSpaceProperty =
            DependencyProperty.Register("AngleSpace", typeof(double), typeof(CanvasArc), new PropertyMetadata(0.0));


        /// <summary>
        /// 外半径
        /// </summary>
        public double RadiusOuter
        {
            get { return (double)GetValue(RadiusOuterProperty); }
            set
            {
                SetValue(RadiusOuterProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for RadiusOuter.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RadiusOuterProperty =
            DependencyProperty.Register("RadiusOuter", typeof(double), typeof(CanvasArc), new PropertyMetadata(100.0, PathArcChangedCallback));


        /// <summary>
        /// 内半径
        /// </summary>
        public double RadiusInner
        {
            get { return (double)GetValue(RadiusInnerProperty); }
            set
            {
                SetValue(RadiusInnerProperty, value);
            }
        }

        /// <summary>
        /// 背景
        /// </summary>
        public Brush BgBrush
        {
            get { return (Brush)GetValue(BgBrushProperty); }
            set { SetValue(BgBrushProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BgBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BgBrushProperty =
            DependencyProperty.Register("BgBrush", typeof(Brush), typeof(CanvasArc), new PropertyMetadata(new SolidColorBrush(Colors.LightGray), (o, e) =>
            {
                var el = (CanvasArc)o;
                if (el != null)
                {
                    el.bgPath.Fill = el.BgBrush;
                }
            }));

        public Brush BgBorderBrush
        {
            get { return (Brush)GetValue(BgBorderBrushProperty); }
            set { SetValue(BgBorderBrushProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BgBorderColor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BgBorderBrushProperty =
            DependencyProperty.Register("BgBorderBrush", typeof(Brush), typeof(CanvasArc), new PropertyMetadata(new SolidColorBrush(Colors.Transparent), (o, e) =>
            {
                var el = (CanvasArc)o;
                if (el != null)
                {
                    el.bgPath.Stroke = el.BgBorderBrush;
                }
            }));



        public double BgBorderWidth
        {
            get { return (double)GetValue(BgBorderWidthProperty); }
            set { SetValue(BgBorderWidthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BgBorderWidth.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BgBorderWidthProperty =
            DependencyProperty.Register("BgBorderWidth", typeof(double), typeof(CanvasArc), new PropertyMetadata(0.0, (o, e) =>
            {
                var el = (CanvasArc)o;
                if (el != null)
                {
                    el.bgPath.Stroke = el.BgBorderBrush;
                }
            }));

        /// <summary>
        /// 边线宽
        /// </summary>
        public double BorderWidth
        {
            get { return (double)GetValue(BorderWidthProperty); }
            set { SetValue(BorderWidthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BorderWidth.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BorderWidthProperty =
            DependencyProperty.Register("BorderWidth", typeof(double), typeof(CanvasArc), new PropertyMetadata(0.0, (o, e) =>
            {
                var el = (CanvasArc)o;
                if (el != null)
                {
                    el.borderPath.StrokeThickness = el.BorderWidth;
                }
            }));



        /// <summary>
        /// 边线样式(虚线，实线),默认null，实线
        /// </summary>
        public DoubleCollection BorderDashArray
        {
            get { return (DoubleCollection)GetValue(BorderDashArrayProperty); }
            set { SetValue(BorderDashArrayProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BorderDashArray.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BorderDashArrayProperty =
            DependencyProperty.Register("BorderDashArray", typeof(DoubleCollection), typeof(CanvasArc), new PropertyMetadata(null, (o, e) =>
            {
                var el = (CanvasArc)o;
                if (el != null)
                {
                    el.borderPath.StrokeDashArray = el.BorderDashArray;
                }
            }));


        // Using a DependencyProperty as the backing store for RadiusInner.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RadiusInnerProperty =
            DependencyProperty.Register("RadiusInner", typeof(double), typeof(CanvasArc), new PropertyMetadata(0.0, PathArcChangedCallback));

        /// <summary>
        /// 边线色彩
        /// </summary>
        /// 


        public Brush BorderColor
        {
            get { return (Brush)GetValue(BorderColorProperty); }
            set { SetValue(BorderColorProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BorderBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BorderColorProperty =
            DependencyProperty.Register("BorderColor", typeof(Brush), typeof(CanvasArc), new PropertyMetadata(new SolidColorBrush(Colors.Gray), (o, e) =>
            {
                var el = (CanvasArc)o;
                if (el != null)
                {
                    el.borderPath.Stroke = el.BorderColor;
                }
            }));

        /// <summary>
        /// Fill层填充色彩
        /// </summary>
        public Brush FillBrush
        {
            get { return (Brush)GetValue(FillBrushProperty); }
            set { SetValue(FillBrushProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FillBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FillBrushProperty =
            DependencyProperty.Register("FillBrush", typeof(Brush), typeof(CanvasArc), new PropertyMetadata(new SolidColorBrush(Colors.Gray), (o, e) =>
            {
                var el = (CanvasArc)o;
                if (el != null)
                {

                    el.fillPath.Fill = el.FillBrush;
                }
            }));

        public Brush FillBorderBrush
        {
            get { return (Brush)GetValue(FillBorderBrushProperty); }
            set { SetValue(FillBorderBrushProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FillBorderBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FillBorderBrushProperty =
            DependencyProperty.Register("FillBorderBrush", typeof(Brush), typeof(CanvasArc), new PropertyMetadata(new SolidColorBrush(Colors.Transparent), (o, e) =>
            {
                var el = (CanvasArc)o;
                if (el != null)
                {
                    el.fillPath.Stroke = el.FillBorderBrush;
                }
            }));


        public double FillBorderWidth
        {
            get { return (double)GetValue(FillBorderWidthProperty); }
            set { SetValue(FillBorderWidthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FillBorderWidth.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FillBorderWidthProperty =
            DependencyProperty.Register("FillBorderWidth", typeof(double), typeof(CanvasArc), new PropertyMetadata(0.0,(o, e) =>
            {
                var el = (CanvasArc)o;
                if (el != null)
                {
                    el.fillPath.StrokeThickness = el.FillBorderWidth;
                }
}));

public double FillPercent
        {
            get { return (double)GetValue(FillPercentProperty); }
            set { SetValue(FillPercentProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FillPercent.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FillPercentProperty =
            DependencyProperty.Register("FillPercent", typeof(double), typeof(CanvasArc), new PropertyMetadata(0.0, (o, e) =>
            {
                var el = (CanvasArc)o;
                if (el != null) { 
                    el.fillPath.Data = ComputedArcPoint(
                        el.AngleStart,
                        el.AngleEnd - el.AngleSpace,
                        el.RadiusInner + (el.RadiusOuter - el.RadiusInner) * el.FillPercent,
                        el.RadiusInner);
                    //el.fillPath.Stroke = new SolidColorBrush(Colors.Red);
                    //el.fillPath.StrokeThickness = el.BorderWidth;
                    //el.fillPath.Fill = el.FillBrush;
                }
            }));



        private static Geometry ComputedArcPoint(double angleStart,double angleEnd,double radiusOuter,double radiusInner)
        {
            //计算扇形
            var startJ = angleStart * Math.PI / 180;
            var endJ = angleEnd * Math.PI / 180;
            var angleX = angleEnd - angleStart;

            var point1 = new Point() { X = radiusInner * Math.Cos(startJ), Y = radiusInner * Math.Sin(startJ) };
            var point2 = new Point() { X = radiusOuter * Math.Cos(startJ), Y = radiusOuter * Math.Sin(startJ) };
            var point3 = new Point() { X = radiusOuter * Math.Cos(endJ), Y = radiusOuter * Math.Sin(endJ) };
            var point4 = new Point() { X = radiusInner * Math.Cos(endJ), Y = radiusInner * Math.Sin(endJ) };
            string arcX = Math.Abs(angleX) % 360 < 180 ? "0" : "1";
            var arcOuterR = "1";//外弧朝向
            var arcInterR = "0";//内弧朝向
            if (angleX < 0)
            {
                arcOuterR = "0";
                arcInterR = "1";
            }
            string data = $"M {point1.X},{point1.Y}";
            data += $"L {point2.X},{point2.Y} ";
            data += $"A {radiusOuter},{radiusOuter} 0 {arcX} {arcOuterR} {point3.X},{point3.Y} ";
            data += $"L {point4.X},{point4.Y} ";
            data += $"A {radiusInner},{radiusInner} 0 {arcX} {arcInterR} {point1.X},{point1.Y} Z";
            return Geometry.Parse(data);
        }
        private static void PathArcChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

            var el = (CanvasArc)d;
            if (el != null)
            {
                //计算扇形
                var angleStart = el.AngleStart;
                var angleEnd = el.AngleEnd - el.AngleSpace;
                var angleX = angleEnd - angleStart;

                el.bgPath.Data = ComputedArcPoint(angleStart, angleEnd, el.RadiusOuter, el.RadiusInner);
                el.bgPath.Fill = el.BgBrush;


                el.borderPath.Data = el.bgPath.Data.Clone();
                el.borderPath.Stroke = el.BorderColor;

                el.fillPath.Data = ComputedArcPoint(
    el.AngleStart,
    el.AngleEnd - el.AngleSpace,
    el.RadiusInner + (el.RadiusOuter - el.RadiusInner) * el.FillPercent,
    el.RadiusInner);

                //计算slot位置
                var slotAngle = angleX / 2 + angleStart;
                var slotJ = slotAngle * Math.PI / 180;
                var slotRadius = (el.RadiusOuter - el.RadiusInner) / 2 + el.RadiusInner;
                var slotPoint = new Point() { X = slotRadius * Math.Cos(slotJ), Y = slotRadius * Math.Sin(slotJ) };

                SetLeft(el.slotBody, slotPoint.X);
                SetTop(el.slotBody, slotPoint.Y);


                if (el.slotInner != null)
                {
                    var slotRadiusOuter = el.RadiusInner - 6;
                    var slotInnerPoint = new Point() { X = slotRadiusOuter * Math.Cos(slotJ), Y = slotRadiusOuter * Math.Sin(slotJ) };
                    el.slotInner.RenderTransform = new TranslateTransform() { X = slotInnerPoint.X - slotPoint.X, Y = slotInnerPoint.Y - slotPoint.Y };
                }

                if(el.slotOuter != null)
                {
                    var slotRadiusInner = el.RadiusOuter + 6;
                    var slotOuterPoint = new Point() { X = slotRadiusInner * Math.Cos(slotJ), Y = slotRadiusInner * Math.Sin(slotJ) };
                    el.slotOuter.RenderTransform = new TranslateTransform() { X = slotOuterPoint.X - slotPoint.X, Y = slotOuterPoint.Y - slotPoint.Y };
                }


            }
        }
        
    }



}
